﻿using System;
using System.Data.Entity;
using System.Linq;

namespace NQueries.Sample
{
    public static class SampleSimpleOperations
    {
        /// <summary>
        /// Creates an object together with an dependent object inside single <see cref="IUnitOfWork"/>
        /// </summary>
        public static void Create()
        {
            Test.CreateDatabase();

            using (IUnitOfWork unitOfWork = UnitOfWork.Begin("Create"))
            {
                Customer customer = new Customer { Name = "Create", Address = new Address { Street = "Create" } };
                unitOfWork.DataContext().Customers.Add(customer);
                unitOfWork.Commit();
            }

            Test.CustomerExists("Create");
        }

        /// <summary>
        /// Deletes a object and its dependent object.
        /// </summary>
        public static void Delete()
        {
            Test.CreateDatabase();
            Test.CreateCustomer("Delete");

            using (IUnitOfWork unitOfWork = UnitOfWork.Begin("Delete"))
            {
                IDbSet<Customer> customers = unitOfWork.DataContext().Customers;
                Customer customer = customers
                    .Include(x => x.Address)
                    .Include(x => x.Companies)
                    .First(x => x.Name == "Delete");

                unitOfWork.DataContext().Addresses.Remove(customer.Address);

                foreach (Company company in customer.Companies.ToArray())
                    unitOfWork.DataContext().Companies.Remove(company);

                customers.Remove(customer);

                unitOfWork.Commit();
            }

            Test.CustomerNotExists("Delete");
            Test.AddressNotExists("Delete");
        }

        /// <summary>
        /// Reads an object by its id.
        /// </summary>
        public static void GetbyIdFromSet()
        {
            int id = Read().Id;

            using (IUnitOfWork unitOfWork = UnitOfWork.Begin("GetbyIdFromSet"))
            {
                Customer customer = unitOfWork.DataContext().Customers.GetById(id);
                if (customer == null)
                    throw new ArgumentNullException();
            }
        }

        /// <summary>
        /// Verifies usage of Include statements.
        /// </summary>
        public static void MultiInclude()
        {
            Test.CreateDatabase();
            Test.CreateCustomer("bla");

            using (IUnitOfWork unitOfWork = UnitOfWork.Begin("MultiInclude"))
            {
                Country country = unitOfWork.DataContext().Countries
                    .Include(x => x.Addresses)
                    .Include(x => x.Addresses.Select(x1 => x1.Customers))
                    .First();

                if (country.Addresses.First().Customers.First().Companies.Any())
                    throw new Exception("include failed, should not be included");

                country = unitOfWork.DataContext().Countries
                    .Include(x => x.Addresses)
                    .Include(x => x.Addresses.Select(x1 => x1.Customers))
                    .Include(x => x.Addresses.Select(x1 => x1.Customers.Select(x2 => x2.Companies)))
                    .First();

                if (!country.Addresses.First().Customers.First().Companies.Any())
                    throw new Exception("include failed, should be included");
            }
        }

        /// <summary>
        /// Nests two unit of works that actually share the same connection, but use different data contexts.
        /// </summary>
        public static void NestedRead()
        {
            Test.CreateDatabase();
            Test.CreateCustomer("NestedRead");

            using (IUnitOfWork outerUnitOfWork = UnitOfWork.Begin("NestedReadOuter"))
            {
                Customer customer = outerUnitOfWork.DataContext().Customers.First();

                using (IUnitOfWork innerUnitOfWork = UnitOfWork.Begin("NestedReadInner"))
                {
                    Address address = innerUnitOfWork.DataContext().Customers.Include(x => x.Address).First(x => x.Id == customer.Id).Address;
                    if (address == null)
                        throw new ArgumentNullException();
                }
            }
        }

        /// <summary>
        /// Read an object with a typed include.
        /// </summary>
        /// <returns></returns>
        public static Customer Read()
        {
            Test.CreateDatabase();
            Test.CreateCustomer("Read");

            using (IUnitOfWork unitOfWork = UnitOfWork.Begin("Read"))
                return unitOfWork.DataContext().Customers.Include(x => x.Address).Single(x => x.Name == "Read");
        }

        /// <summary>
        /// Read an object, update it and write back to database.
        /// </summary>
        public static void Update()
        {
            Test.CreateDatabase();
            Test.CreateCustomer("Update");

            using (IUnitOfWork unitOfWork = UnitOfWork.Begin("Update"))
            {
                Customer customer = unitOfWork.DataContext().Customers.First();
                customer.Name = "Update2";
                unitOfWork.Commit();
            }

            Test.CustomerExists("Update2");
        }

        /// <summary>
        /// Modifies a detached entity and verifies changes saved to the db.
        /// </summary>
        public static void UpdateDetachedEntity()
        {
            Test.CreateDatabase();
            Test.CreateCustomer("UpdateDetachedEntity");

            Customer customer;

            using (IUnitOfWork unitOfWork = UnitOfWork.Begin("UpdateDetachedEntity1"))
                customer = unitOfWork.DataContext().Customers.Single(x => x.Name == "UpdateDetachedEntity");

            customer.Name = "modified";

            using (IUnitOfWork unitOfWork = UnitOfWork.Begin("UpdateDetachedEntity2"))
            {
                unitOfWork.DataContext().Customers.Update(customer);
                unitOfWork.Commit();
            }

            using (IUnitOfWork unitOfWork = UnitOfWork.Begin("UpdateDetachedEntity3"))
                if (unitOfWork.DataContext().Customers.Single(x => x.Name == "modified") == null)
                    throw new Exception("failed");
        }

        /// <summary>
        /// Shows how to specify a command timout.
        /// </summary>
        public static void UseCommandTimeout()
        {
            using (IUnitOfWork unitOfWork = UnitOfWork.Begin("UseCommandTimeout"))
                unitOfWork.DataContext.CommandTimeout = 500;
        }

        /// <summary>
        /// Verifies that the relation fixups are working.
        /// </summary>
        public static void VerifyRelationFixups()
        {
            Address address = new Address();
            Customer customer = new Customer { Address = address };
            Country country = new Country();
            country.Addresses.Add(address);
            if (!address.Customers.Contains(customer))
                throw new Exception("failed");

            if (address.Country != country)
                throw new Exception("failed");
        }
    }
}